package com.jiatuxueyuan.the4.remoteio;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.group.ChannelGroup;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import io.netty.util.ReferenceCountUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * ZLAN 6842 RemoteIO设备解码，负责解码6842设备发过来的Modbus TCP报文，注意：这个Zlan6842InboundHandler对象不是单例，它是每个客户端Channel都有自己的Zlan6842InboundHandler实例，
 * 所以是线程安全的。
 */
public class Zlan6842InboundHandler extends ChannelInboundHandlerAdapter {

    private ChannelGroup channelGroup;

    public Zlan6842InboundHandler(ChannelGroup channelGroup) {
        this.channelGroup = channelGroup;
    }

    private static Logger logger = LoggerFactory.getLogger(Zlan6842InboundHandler.class);

    @Override
    protected void ensureNotSharable() {
        super.ensureNotSharable();
    }

    @Override
    public boolean isSharable() {
        return true;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel client = ctx.channel();
        channelGroup.add(client);
        if(logger.isInfoEnabled()) {
            logger.info("一个新的客户端连接上了：" + client.remoteAddress().toString() + "，当前在线客户端数量：" + channelGroup.size());
        }
        super.channelActive(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        super.channelInactive(ctx);
        System.out.println("执行了Zlan6842Handler的channelInactive方法，客户端下线了，当前在线客户端数量：" + channelGroup.size());
    }

    /**
     * 负责解析ZLAN6842远程IO模块发过来的TCP报文，有2种报文：
     * 1. 上电时发过来的MAC地址，唯一标识一台ZLAN6842
     * 2. Modbus TCP报文
     *
     * @param ctx
     * @param msg
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        Attribute<String> value = ctx.channel().attr(AttributeKey.valueOf("client_mac"));
        String client = value.get() != null ? value.get() : "";
        ByteBuf buf = (ByteBuf) msg;
        int readableBytes = buf.readableBytes();
        if(readableBytes < 6) {
            /*
                ZLAN6842不可能发送少于6个字节的报文，因此假如有少于6字节的报文，肯定是非法报文，直接丢弃，netty的ByteBuf是一个引用计数对象，要让它被释放，必须明确调用release方法
                netty明确指出，传递给Handler的ByteBuf对象，应该由Handler自己负责释放，一般可以调用ReferenceCountUtil.release(msg)方法释放
             */
            System.err.println("接到客户端发过来的字节流，但是不满6个字节：" + ByteBufUtil.hexDump(buf));
            //2种写法，ReferenceCountUtil.release(buf)可以直接丢掉这些字节，或者直接return也会直接丢掉这些字节
            return;
//            ReferenceCountUtil.release(buf);
        } else if(readableBytes == 6) { //远程IO模块上电，建立TCP连接后第一时间会发送自己的MAC地址，通知服务端自己上线了
            byte[] mac = new byte[6];
            buf.readBytes(mac);
            String clientMacAddress = ByteBufUtil.hexDump(mac).toUpperCase();
            ctx.channel().attr(AttributeKey.valueOf("client_mac")).set(clientMacAddress);
            System.out.println("接到客户端发过来的6字节MAC地址，存入Channel#attr中：" + clientMacAddress);
        } else {
            System.out.println(String.format("收到字节流，客户端MAC：%s, 字节总数：%d，转换为16进制表示：%s", client, readableBytes, ByteBufUtil.hexDump(buf)));
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        super.channelReadComplete(ctx);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        super.userEventTriggered(ctx, evt);
    }

    @Override
    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
        super.channelWritabilityChanged(ctx);
    }

    /**
     * 比如客户端主动断开连接，此时Server端会出现：
     * An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
     *
     * java.io.IOException: 远程主机强迫关闭了一个现有的连接。
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//        super.exceptionCaught(ctx, cause);
        logger.error("IO异常，可能客户端下线或者网络失联");
    }

    /**
     * 只要有一个新的客户端连接上来，就会为这个客户端调用这个方法！
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        logger.info("handlerAdded被调用");
        super.handlerAdded(ctx);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        logger.info("Zlan6842Handler#handlerRemoved方法被调用");
        super.handlerRemoved(ctx);
    }
}